﻿// ***********************************************************************
// Assembly         : DonNicky.Common
// Author           : donnicky
// Created          : 11-19-2012
//
// Last Modified By : donnicky
// Last Modified On : 12-02-2012
// ***********************************************************************
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Resources;
using DonNicky.Common.Resources;

namespace DonNicky.Common
{
    /// <summary>
    /// Reflection helpers.
    /// </summary>
    public static class Reflection
    {
        /// <summary>
        /// Gets the name of the property.
        /// </summary>      
        /// <typeparam name="T">The type to get a property of.</typeparam>
        /// <param name="propertyExpression">The property expression. Example: <code>x => x.SomeProperty</code></param>
        /// <returns>Name of the property.</returns>
        /// <exception cref="System.ArgumentNullException">propertyExpression</exception>
        /// <exception cref="System.ArgumentException">propertyExpression</exception>
        /// <example><code>Reflection.GetProperty(x => x.SomeProperty)</code></example>
        public static string GetPropertyName<T>(Expression<Func<T, object>> propertyExpression)
        {
            if (propertyExpression == null)
                throw new ArgumentNullException("propertyExpression");

            var memberInfo = GetPropertyInformation(propertyExpression.Body);
            if (memberInfo == null)
                throw new ArgumentException(Messages.NoPropertyReference, "propertyExpression");

            return memberInfo.Name;
        }

        /// <summary>
        /// Gets the display name of the property as defined in DisplayAttribute (including base class or interface attribute definition for inherited property).
        /// </summary>
        /// <typeparam name="T">The type to get a property of.</typeparam>
        /// <param name="propertyExpression">The property expression. Example: <code>x => x.SomeProperty</code></param>
        /// <param name="shortName">If set to <c>true</c> ShortName property of DisplayAttribute is taken.</param>
        /// <returns>Display name of the property.</returns>
        /// <exception cref="System.ArgumentNullException">propertyExpression</exception>
        /// <exception cref="System.ArgumentException">propertyExpression</exception>
        /// /// <example><code>Reflection.GetPropertyDisplayName(x => x.SomeProperty)</code></example>
        // ReSharper disable UnusedMember.Global
        public static string GetPropertyDisplayName<T>(Expression<Func<T, object>> propertyExpression, bool shortName = false)
        // ReSharper restore UnusedMember.Global
        {
            if (propertyExpression == null)
                throw new ArgumentNullException("propertyExpression");

            var memberInfo = GetPropertyInformation(propertyExpression.Body);
            if (memberInfo == null)
                throw new ArgumentException(Messages.NoPropertyReference, "propertyExpression");

            return GetMemberDisplayName(memberInfo, shortName);
        }

        /// <summary>
        /// Gets the display name of the property as defined in DisplayAttribute (including base class or interface attribute definition for inherited property).
        /// </summary>
        /// <typeparam name="T">The type to get a property of.</typeparam>
        /// <param name="propertyName">Name of the property.</param>
        /// <param name="shortName">If set to <c>true</c> ShortName property of DisplayAttribute is taken.</param>
        /// <returns>Display name of the property.</returns>
        public static string GetPropertyDisplayName<T>(string propertyName, bool shortName = false)
        {
            return GetPropertyDisplayName(typeof(T), propertyName, shortName);
        }

        /// <summary>
        /// Gets the display name of the property as defined in DisplayAttribute (including base class or interface attribute definition for inherited property).
        /// </summary>
        /// <param name="type">The type to get a property of.</param>
        /// <param name="propertyName">Name of the property.</param>
        /// <param name="shortName">If set to <c>true</c> ShortName property of DisplayAttribute is taken.</param>
        /// <returns>Display name of the property.</returns>
        /// <exception cref="System.ArgumentNullException">type</exception>
        /// <exception cref="System.ArgumentException">propertyName</exception>
        public static string GetPropertyDisplayName(Type type, string propertyName, bool shortName = false)
        {
            if (type == null)
                throw new ArgumentNullException("type");
            if (propertyName == null)
                throw new ArgumentNullException("propertyName");
#if PORTABLE
            var memberInfo = type.GetMember(propertyName, BindingFlags.Public | BindingFlags.Instance).SingleOrDefault();
#else
            var memberInfo = type.GetMember(propertyName, MemberTypes.Property, BindingFlags.Public | BindingFlags.Instance).SingleOrDefault();
#endif
            if (memberInfo == null)
                throw new ArgumentException(string.Format("{0} ({1})", Messages.NoPropertyFound, propertyName), "propertyName");

            return GetMemberDisplayName(memberInfo, shortName);
        }

        /// <summary>
        /// Gets the display name of the member as defined in DisplayAttribute (including base class or interface attribute definition if inherited).
        /// </summary>
        /// <param name="memberInfo">The member info.</param>
        /// <param name="shortName">If set to <c>true</c> ShortName property of DisplayAttribute is taken.</param>
        /// <returns>Display name of the member.</returns>
        /// <exception cref="System.ArgumentNullException">memberInfo</exception>
        public static string GetMemberDisplayName(MemberInfo memberInfo, bool shortName = false)
        {
            if (memberInfo == null)
                throw new ArgumentNullException("memberInfo");

            var attr = memberInfo.GetAttribute<DisplayAttribute>();
            // проанализировать ResourceType и оттуда по attr.Name выбрать значение ресурса
            if (attr == null)
                return memberInfo.Name;
            if (attr.ResourceType == null)
                return (shortName ? (attr.ShortName ?? attr.Name) : attr.Name) ?? memberInfo.Name;
            var resManProp = attr.ResourceType.GetProperty("ResourceManager");
            if (resManProp == null)
                return memberInfo.Name;
            var resMan = resManProp.GetValue(null, null) as ResourceManager;
            if (resMan == null)
                return memberInfo.Name;
            var resourceName = shortName ? (attr.ShortName ?? attr.Name) : attr.Name;
            if (resourceName == null)
                return memberInfo.Name;
            var result = resMan.GetString(resourceName);
            return result ?? memberInfo.Name;
        }

        /// <summary>
        /// Gets the attribute of the member (including attribute definitions from base types if inherited).
        /// </summary>
        /// <typeparam name="T">The type.</typeparam>
        /// <param name="member">The member.</param>
        /// <param name="inherit">If set to <c>true</c> attribute definitions from base types are considered.</param>
        /// <returns>The attribute instance.</returns>
        /// <exception cref="System.ArgumentNullException">member</exception>
        // ReSharper disable MemberCanBePrivate.Global
        public static T GetAttribute<T>(this MemberInfo member, bool inherit = true)
            // ReSharper restore MemberCanBePrivate.Global
            where T : Attribute
        {
            if (member == null)
                throw new ArgumentNullException("member");

            var attribute = member.GetAllCustomAttributes(typeof(T), inherit).SingleOrDefault();
            return (T)attribute;
        }

        /// <summary>
        /// Gets all custom attributes (including attribute definitions from base types if inherited).
        /// </summary>
        /// <typeparam name="T">The type.</typeparam>
        /// <param name="memberInfo">The member info.</param>
        /// <param name="inherit">If set to <c>true</c> attribute definitions from base types are considered.</param>
        /// <returns>An enumerable that contains all the custom attributes.</returns>
        public static IEnumerable<T> GetAllCustomAttributes<T>(this MemberInfo memberInfo, bool inherit = true)
        {
            return GetAllCustomAttributes(memberInfo, typeof(T), inherit).Cast<T>();
        }

        /// <summary>
        /// Gets all custom attributes (including attribute definitions from base types if inherited).
        /// </summary>
        /// <param name="memberInfo">The member info which is searched for the attribute.</param>
        /// <param name="attributeType">The type of attribute to search for.</param>
        /// <param name="inherit">Specifies whether to search this member's inheritance chain to find the attribute. Interfaces will be searched, too.</param>
        /// <returns>An array that contains all the custom attributes, or an array with zero elements if no attributes are defined.</returns>
        /// <exception cref="System.ArgumentNullException">memberInfo</exception>
        public static object[] GetAllCustomAttributes(this MemberInfo memberInfo, Type attributeType, bool inherit = true)
        {
            if (memberInfo == null) throw new ArgumentNullException("memberInfo");
            if (attributeType == null) throw new ArgumentNullException("attributeType");
            if (!inherit)
                return memberInfo.GetCustomAttributes(attributeType, false);
            var declaringType = memberInfo.DeclaringType ?? (Type)memberInfo;
            var result = new Collection<object>();
            if (declaringType != memberInfo)
            {
                var baseType = declaringType;
                do
                {
                    // ReSharper disable PossibleNullReferenceException
                    var baseTypeMemberInfo = baseType.GetMember(memberInfo.Name).SingleOrDefault();
                    // ReSharper restore PossibleNullReferenceException
                    if (baseTypeMemberInfo != null)
                        baseTypeMemberInfo.GetCustomAttributes(attributeType, true).ForEach(result.Add);
                    baseType = baseType.BaseType;
                } while (baseType != null);

                foreach (var interfaceType in declaringType.GetInterfaces())
                {
                    var interfaceMemberInfo = interfaceType.GetMember(memberInfo.Name).SingleOrDefault();
                    if (interfaceMemberInfo != null)
                        interfaceMemberInfo.GetCustomAttributes(attributeType, true).ForEach(result.Add);
                }
            }
            else
            {
                declaringType.GetCustomAttributes(attributeType, true).ForEach(result.Add);
                foreach (var interfaceType in declaringType.GetInterfaces())
                    interfaceType.GetCustomAttributes(attributeType, true).ForEach(result.Add);
            }
            return result.ToArray();
        }

        private static MemberInfo GetPropertyInformation(Expression propertyExpression)
        {
            var memberExpr = propertyExpression as MemberExpression;
            if (memberExpr == null)
            {
                var unaryExpr = propertyExpression as UnaryExpression;
                if (unaryExpr != null && unaryExpr.NodeType == ExpressionType.Convert)
                    memberExpr = unaryExpr.Operand as MemberExpression;
            }

#if PORTABLE
            if (memberExpr != null)
#else
            if (memberExpr != null && memberExpr.Member.MemberType == MemberTypes.Property)
#endif
            {
                return memberExpr.Member;
            }

            return null;
        }
    }
}
